package org.clusterbomb.core.engine;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.clusterbomb.core.context.DeploymentContext;
import org.clusterbomb.core.context.PluginInvocationContext;
import org.clusterbomb.core.job.DeploymentLifecycle;
import org.clusterbomb.core.job.DeploymentJob.PhaseBinding;
import org.clusterbomb.core.job.DeploymentJob.ServerGroup;
import org.clusterbomb.core.job.DeploymentJob.ServerGroup.Server;
import org.clusterbomb.core.job.DeploymentLifecycle.Stage;
import org.clusterbomb.core.plugin.ClusterBombPlugin;

public class JobExecutor implements Runnable {
	
	private ExecutorService serverStageExecutor;
	
	private final DeploymentLifecycle lifeCycle;
	private final DeploymentContext context;
	
	public JobExecutor(DeploymentLifecycle lifeCycle, DeploymentContext context) {
		this.context = context;
		this.lifeCycle = lifeCycle;
	}
	
	public void run() {
	
		processDeploymentStart();
		
		for (ServerGroup group : context.getJob().getServerGroup()) {
			
			processServerGroup(group);
			
		}
		
		processDeploymentEnd();
		
		
	}
	
	private void processServerGroup(ServerGroup group) {
	
		processServerGroupStart(group);
	
		serverStageExecutor = Executors.newFixedThreadPool(context.getJob().getMaxThreads());
		
		Collection<ServerDeploymentProcess> tasks = new LinkedList<ServerDeploymentProcess>();
			
		ServerDeploymentProcess proc;
		for (Server server : group.getServer()) {
			proc = new ServerDeploymentProcess(group, server);
			tasks.add(proc);
		}
		
		try {
			for (Future<?> f : serverStageExecutor.invokeAll(tasks)) {
				f.get();
			//post progress, fire listener event
			}
		}
		catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		catch (ExecutionException e) {
			throw new RuntimeException(e);
			
		}
		processServerGroupEnd(group);
		
				
		
	}
	
	
	
	private Stage locateLifecyleStage(DeploymentStage stage) {
		
		for (Stage s : lifeCycle.getStage()) {
			if (s.getStageId().equals(stage.getId())) {
				return s;
			}
		}
		
		throw new IllegalStateException("Lifecyle does not support required stage: " + stage.getId());
		
	}
	
	private Collection<PhaseBinding> locatePhaseBindings(String phaseId) {
		
		List<PhaseBinding> bindings = new ArrayList<PhaseBinding>();
		
		for (PhaseBinding binding : context.getJob().getPhaseBinding()) {
			if (binding.getPhase().equals(phaseId)) {
				bindings.add(binding);
			}
		}
		
		return bindings;
		
	}
	
	protected void processStage(final DeploymentStage stage) {
		
		processStage(stage, null, null);
		
	}
	
	protected PluginInvocationContext createInvocation(DeploymentStage stage, PhaseBinding binding, ServerGroup group, Server server) {
		
		return new PluginInvocationContext();
	}
	
	protected ClusterBombPlugin locatePlugin(String pluginId) {
		
		return null;
	}
	
	protected void processDeploymentStart() {
		
		processStage(DeploymentStage.DEPLOYMENT_START);
		
	}
	
	protected void processDeploymentEnd() {
		
		processStage(DeploymentStage.DEPLOYMENT_END);
		
	}
	
	protected void processServerGroupStart(ServerGroup group) {
		
		processStage(DeploymentStage.SERVER_GROUP_START, group, null);
		
	}
	
	protected void processServerGroupEnd(ServerGroup group) {
		
		processStage(DeploymentStage.SERVER_GROUP_START, group, null);
		
	}
	
	
	
	
	protected void processStage(final DeploymentStage stage, final ServerGroup group, final Server server) {
		
		
		PluginInvocationContext pluginContext;
		ClusterBombPlugin plugin;
		
		for (String phaseId : locateLifecyleStage(stage).getPhaseId()) {
			
			for (PhaseBinding binding : locatePhaseBindings(phaseId)) {
				pluginContext = createInvocation(stage, binding, group, server);
				plugin = locatePlugin(binding.getPluginId());
				try {
					plugin.execute(pluginContext);
				}
				catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
			
		
		
		
	}
	
	
	private class ServerDeploymentProcess implements Callable<ServerDeploymentResult> {

		
		private final Server server;
		private final ServerGroup group;
		
		public ServerDeploymentProcess(final ServerGroup group, final Server server) {
			this.group = group;
			this.server = server;
		}
		
		@Override
		public ServerDeploymentResult call() throws Exception {
			
			
			processStage(DeploymentStage.SERVER, group, server);
			
			
			
			return null;
		}
		
		
		
		
		
	}
	
}
